1. Введение
MyBlog – это легковесное асинхронное веб-приложение для ведения личного блога и галереи нейросетевых артов. Построено на базе фреймворка FastAPI. Основная идея проекта – полная автономность. Отсутствие тяжёлых внешних зависимостей (баз данных) и работа через высокоскоростное In-memory кэширование файловой системы. Это сочетает надёжность хранения в плоских файлах (Markdown) с мгновенным откликом интерфейса. Это делает проект идеальным для self-hosted развёртывания.
2. Архитектура и серверная логика: main.py
2.1 Инфраструктурные решения
Проект спроектирован с учётом работы за Reverse Proxy (Nginx/Traefik), что делает его максимально гибким для различных сценариев хостинга.
- Middleware для подпапок:
@app.middleware("http")
async def add_proxy_headers(request: Request, call_next):
path_prefix = request.headers.get("X-Forwarded-Prefix")
if path_prefix:
request.scope["root_path"] = path_prefix
return await call_next(request)
- Это критически важный узел. Если сайт крутится не в корне домена, заголовки
X-Forwarded-Prefixпозволяют FastAPI корректно генерировать внутренние ссылки. Без этой прослойки статика и переходы были бы полностью разрушены. - Система реактивного кэширования (In-memory Cache) Для достижения экстремальной производительности при любом количестве статей внедрена система кэширования в оперативной памяти. Сервер индексирует директорию
/postsтолько в три момента: при запуске приложения (асинхронный контекстный менеджерlifespan), при загрузке новой статьи и при удалении. Во всех остальных случаях список статей отдаётся из памяти мгновенно, минуя обращения к жёсткому диску. Это снижает нагрузку на I/O и делает главную страницу неуязвимой для тормозов даже при наличии тысяч файлов. - Автономная загрузка окружения: Реализована функция
load_env_file, выполняющая парсинг.envбез сторонних библиотек. Поддержка UTF-8 гарантирует безошибочную работу кириллических паролей. - Поддержка служебных префиксов: Логика парсинга имён файлов учитывает дескрипторы:
!для закрепления и[]для категорий. - Выбор XHR для загрузки файлов: Использование
xhr.upload.onprogressпозволило реализовать «честный» прогресс-бар для больших медиафайлов. - Динамическое управление путями: Реализована система рекурсивного сканирования. Фронтенд позволяет выбрать подпапку или создать новую «на лету». Визуальное превью пути через
oninputисключает ошибки линковки. - Изолированное удаление (Safe Rmtree): Реализован эндпоинт
/delete_folder. Для предотвращения критических ошибок введена система Forbidden Folders (запрет на удаление корня и системных папок). - Рекурсивная индексация хранилища: Алгоритм
STATIC_DIR.rglob("*")позволяет серверу «видеть» всю глубину вложенности папок в реальном времени, превращаяstaticв структурированное облако контента. - Двухуровневая система блюра: Серверная логика распознаёт суффикс
_Sв именах файлов (например,image_S.jpg). Это служит «железным» маркером для автоматического наложения блюра в Галерее, гарантируя безопасность контента без ручной правки кода.
- Оптимизация длительных сессий (Timeout Handling): Для поддержки загрузки сверхтяжёлых медиафайлов (видео, архивы) в конфигурацию ASGI-сервера внедрён параметр--timeout-keep-alive 600. Это предотвращает разрыв соединения со стороны сервера при длительной передаче данных, что в сочетании с XHR-прогресс-баром обеспечивает надёжный user-experience при работе с файлами объёмом более 1 ГБ.
2.2 Работа с базой данных (Flat-file DB)
Архитектура проекта исключает SQL. Мы используем Flat-file approach:
- Статьи:
.mdфайлы в/posts. - Метаданные галереи:
prompts.json. - Коды доступа к статьям:
passwords.json. - Медиа:
static/gallery. - Atomic Folder Deletion: Эндпоинт удаления папок использует
shutil.rmtree, но защищён механизмом «мягкой блокировки» для системных путей. - Гибридная логика доступа: Физическое хранение в плоских файлах сочетается с высокоскоростным индексированием в оперативную память. Это даёт надёжность хранения данных и скорость выдачи контента уровня Redis без установки дополнительных баз данных.
Плюсы: Бэкап блога – это просто копирование папки. Нет оверхеда на коннекты к БД. Скорость доступа к списку постов больше не ограничена скоростью дисковой подсистемы.
Минусы: Отсутствие транзакционности, что полностью нивелируется использованием кэша и атомарных операций записи для персонального блога.
2.3 Авторизация и механизм приватных статей
Реализована через SessionMiddleware на базе зашифрованных Cookies. Функция require_auth выступает барьером для всех деструктивных действий.
Внедрена система дискретных кодов доступа для статей с префиксом [P].
- Жизненный цикл кодов: Прямо из интерфейса автор может выполнить ротацию ключа (Rotate Key) – генерацию нового пароля с атомарной перезаписью в изолированный файл passwords.json. Системный .env остаётся неизменным. Алгоритм
Cleanup Keysудаляет изpasswords.jsonнеактуальные ключи для уже стёртых статей. Все операции с базой паролей опираются на единую функцию нормализацииclean_article_name, которая одинаково срезает префиксы ! и [P] во всех эндпоинтах, исключая рассинхрон при любом порядке тегов. - Интеллектуальный шлюз (Smart Access): Система авторизации автоматически распознаёт контекст запроса. Если пользователь переходит по прямой ссылке на приватную статью (префикс
[P]), форма входа динамически трансформируется: поле «Логин» скрывается, а интерфейс предлагает только «Введите код доступа». Это реализовано через проверкуnext_urlв шаблоне Jinja2, что обеспечивает бесшовный UX – гостю не нужно знать системный логинguest, достаточно иметь секретный шестизначный код. - Криптостойкость генерации: Коды формируются через модуль
secrets(CSPRNG), исключая псевдослучайные уязвимостиrandom, а переход на 6-значный формат увеличил пространство вариантов до 1 000 000 комбинаций
2.4 Контент-пайплайн (Markdown)
Сервер выполняет базовую конвертацию Markdown в HTML с расширениями fenced_code, tables, nl2br, sane_lists, attr_list, md_in_html и toc. Расширенный синтаксис заметок Obsidian (блоки Callouts вроде [!INFO], [!WARNING], маркерное выделение ==текст== и интерактивные чек-листы [x]) дорабатывается на клиентской стороне скриптами пост-процессинга в article.html. Это объединяет надёжность серверного HTML с гибкостью визуального обогащения.
2.5 Автоматизация путей через Post-processing
html = html.replace('/blog/static/', '/blog/static/')
Механизм автоматически заменяет относительные пути на абсолютные перед отдачей страницы пользователю. Это гарантирует, что медиаконтент будет корректно отображаться вне зависимости от вложенности URL статьи, а запросы к статике будут мгновенно обрабатываться через Nginx (блок /blog/static/), минуя Python-логику. Это обеспечивает «железную» стабильность отображения картинок и видео.
- UI/UX контроль: Интеллектуальный Path-Preview транслирует имя папки в путь
/static/.... Система дополнена Live-List: при выборе папки мгновенно разворачивается список её файлов. - Техническая безопасность: Поддержка вложенных структур реализована через разрешение символа слеша
\/. Система фильтрации гарантирует защиту от атак Path Traversal.
2.6 Функциональные эндпоинты и безопасность хранилища
2.6.1 Галерея и Промты
Галерея – это не просто список картинок. Сервер связывает файлы изображений с их текстовыми описаниями (промтами):
GET /gallery: Сканирует директорию, сортирует файлы по времени изменения (st_mtime) и сопоставляет с данными из JSON.POST /gallery/prompt_save: Обновляет описания. Поддерживается только админом.
2.6.2 Универсальный медиа-хаб и безопасность загрузки
- Интегрированный менеджер файлов (File Explorer): Внедрён асинхронный поиск через эндпоинт
/list_files/{folder_name:path}. Использование типизации:pathв маршруте позволяет работать со сложными путями. - Безопасность имён (Sanitization): Регулярное выражение
re.sub(r'[^\w\s\-\.\[\]\(\),!«»"\'\u00A0—;?]', ...)очищает имена файлов, сохраняя типографику (ёлочки, тире) и управляющие символы. - Валидация типов: Проверка на уровне
content_typeи «белого списка» расширений (WebP, SVG и др.) гарантирует безопасность хранилища. - Контекстный UI: Интерактивные метки (Folder Tags) и скрытые кнопки удаления папок (эффект
:hover) сохраняют чистоту интерфейса. - Кастомная навигация: Авторская стилизация скроллбара в акцентных цветах в тёмной теме.
- Иерархическая навигация (Smart Folder Tags): Реализована логика динамического «раскрытия» дерева директорий. Чтобы избежать визуального загромождения интерфейса (эффект «каши»), система принудительно скрывает вложенные пути. Метки подпапок (например,
pictures/kartozz) становятся видимыми только при активации родительского раздела (pictures). Это обеспечивает фокус на текущем контексте работы и делает навигацию интуитивно понятной даже при экстремально глубокой вложенности архива. - Визуальный отклик (Active States): Метки папок получили динамическую подсветку (
.active), что в сочетании с живым превью пути (previewPath) исключает ошибки при загрузке контента. При клике на метку происходит не только смена целевой папки, но и мгновенная перефильтрация видимых тегов на стороне клиента. - Опция «Блюр при загрузке»: В интерфейс загрузчика интегрирован чекбокс, который при активации автоматически переименовывает файл на стороне сервера, добавляя суффикс
_S. Серверная логика и шаблоны автоматически распознают этот маркер, применяя к медиафайлу эффект блюра и плашку NSFW, создавая безопасную среду для просмотра контента. - Мультимедийная экспансия: Обновлённый список разрешённых расширений теперь включает формат .mp3. Это трансформирует MyBlog из текстового движка в полноценную мультимедийную платформу, позволяя автору интегрировать аудиоподкасты и голосовые заметки в структуру статей, сохраняя при этом общую асинхронность и скорость отдачи контента.
- Стойкость к сетевым задержкам: Благодаря расширенным серверным таймаутам, MyBlog успешно завершает операции записи на диск даже при медленном исходящем соединении у пользователя.
2.7 Шаблонизация, типографика и фильтры Jinja2
В конфигурацию шаблонизатора внедрён кастомный фильтр insert_zwsp:
templates.env.filters.update({"insert_zwsp": insert_zero_width_spaces})
Он расставляет невидимые мягкие переносы (\u200b / ZWSP) исключительно в аномально длинные последовательности символов (более 20 символов), не затрагивая HTML-теги. Порог в 20 знаков выбран осознанно: это полностью исключает разрывы обычных длинных русских слов и сохраняет целостность типографики. Фильтр надёжно спасает мобильную вёрстку от распирания сверхдлинными ссылками и техническими строками.
2.7.1 Типографика и ёфикация
Проект следует строгому стандарту ёфикации контента. Использование буквы «ё» во всех материалах и заголовках блога – фундаментальное правило чистоты языка. Фильтр insert_zwsp бережно защищает слова с буквой «ё» от некорректных переносов, сохраняя эстетику авторского текста
2.8 Визуальный конструктор: Blog Master
В экосистему MyBlog интегрирован мощный визуальный конструктор, позволяющий создавать профессиональные обложки и карточки-анонсы в стиле Flexy без использования Photoshop.
- WYSIWYG-редактирование: Реализован полноценный визуальный редактор на базе
contenteditable. Автор может менять иерархию заголовков (H1, H2, P), настраивать выравнивание и добавлять акцентные блоки (Quotes, Callouts) прямо на лету. - Динамическая работа с медиа: Внедрена функция «умной» вставки изображений через Drag-and-Drop или буфер обмена (
Ctrl+V). Система позволяет интерактивно управлятьY-offset(смещением) картинки внутри карточки, добиваясь идеальной композиции. - Форматирование без лишнего кода: Внедрена система «тихой» очистки вставляемого текста. При вставке извне скрипт автоматически удаляет лишние HTML-теги, сохраняя только чистый текст, что гарантирует стабильность финальной вёрстки.
- Многоформатный экспорт (PNG / WebP): Благодаря библиотеке
html2canvas(подключаемой по абсолютному пути/blog/static/js/html2canvas.min.jsбез внешних CDN) реализован рендеринг DOM-дерева в растровое изображение с масштабомscale: 2. Это обеспечивает высокую чёткость графики для Retina-дисплеев. Поддержка формата WebP позволяет получать обложки с минимальным весом при сохранении 90% качества. - Мультиформатная гибкость (Cross-Platform): Конструктор поддерживает мгновенное переключение между популярными форматами: от классического 16:9 (YouTube/Web) до 1:1 (Square) и портретного Insta 4:5. Система автоматически масштабирует рабочую область для комфортного редактирования на любых мониторах, сохраняя при этом Pixel Perfect точность при экспорте
[!WARNING] Исключительно для ПК
Конструктор является профессиональным инструментом и доступен только в десктопной версии. В мобильных браузерах доступ намеренно ограничен, так как корректная вёрстка и рендеринг шрифтов требуют строго определённой ширины экрана. Только на ПК рабочая область гарантирует режим Pixel Perfect: когда финальный файл в форматах PNG или WebP в точности соответствует тому, что вы видите на своём мониторе, без нежелательных переносов строк или искажений.
Благодаря интеллектуальному сбросу масштаба перед рендерингом, вы получаете кристально чистое изображение в исходном высоком разрешении, независимо от выбранного формата холста.
3. Клиентская часть: main.html (Главная страница)
3.1 UI/UX и дизайн-система
Дизайн выполнен в стиле Modern Dark Minimalism.
В свежей итерации интерфейс стал более симметричным и сфокусированным. Основные изменения коснулись визуальной иерархии:
- Центрированная композиция: Блок поиска (
#searchInput) и облако тегов теперь выровнены по центру, что создаёт более сбалансированный вид на десктопах. Поиск получил эффектbackdrop-filter: blur(10px), подчёркивающий глубину интерфейса. - Умные карточки статей: Каждая плитка списка теперь имеет увеличенный верхний отступ (
45px), чтобы освободить пространство под динамический бейдж категории. Плавный подъём карточки при наведении (translateY(-4px)) дополнен глубокой тенью, выделяющей активный контент. - Zero-runtime Icons: Иконка поста (
.post-icon) сохранила свою лёгкость, отрисовываясь на чистом CSS через псевдоэлементы, что гарантирует мгновенный рендеринг без внешних запросов. - Backend-фильтрация контента: В отличие от решений, где приватный контент просто помечается замочком, в MyBlog реализовано полное сокрытие. Если пользователь не авторизован, эндпоинт
/mainполностью исключает из массиваdisplay_postsфайлы с меткой[P]. Случайный гость даже в исходном коде страницы не увидит названий закрытых материалов.
3.2 Динамическая генерация категорий (The Tag System)
Одной из ключевых фишек интерфейса является автоматическая трансформация структуры карточек при загрузке страницы. Система на лету считывает метаданные из названий файлов и модифицирует DOM-дерево для каждой статьи.
- Логика работы: Скрипт
initCategoriesанализирует атрибутdata-name. Если название файла начинается с квадратных скобок (например,[Soft] Прошивка.md), запускается алгоритм визуального разделения контента. - Модификация заголовков: Из видимой части названия статьи автоматически вырезается технический префикс в скобках. Это позволяет хранить файлы в структурированном виде, но отображать их максимально чисто и эстетично.
- Внедрение визуальных бейджей: В начало каждой карточки (
li) скрипт принудительно добавляет элемент.post-category. Это создаёт эффектный графический маркер в углу плитки, который визуально отделяет тематику от основного текста.
Техническая реализация:
// Ищем все конструкции в квадратных скобках
const matches = fullName.match(/\[(.*?)\]/g);
let catName = "";
if (matches) {
matches.forEach(m => {
const content = m.slice(1, -1);
if (content.toUpperCase() === 'P') {
item.dataset.private = "true"; // Флаг приватности [P]
} else {
catName = content; // Имя категории
}
});
}
Этот подход полностью избавляет от ручного прописывания тегов в базе данных. Система сама строит дерево категорий и формирует облако тегов в верхней части страницы (categoryBox) исключительно на базе реальных префиксов в именах файлов. Плавное переключение между разделами происходит мгновенно на стороне клиента, обеспечивая безупречный UX.
3.3 Система приоритетов (Sticky Posts)
В скрипт initCategories интегрирован алгоритм проверки символа !.
- Если имя файла начинается с восклицательного знака, карточка получает CSS-класс
.pinned. - Такие статьи визуально выделяются акцентной полосой слева и иконкой гвоздика 📌
- Логика очистки заголовка автоматически удаляет
!из видимого текста, оставляя название чистым. - Визуальная индикация закрепов: Помимо иконки гвоздика 📌, закреплённые статьи выделяются за счёт CSS-класса
.pinned. Мы добавили эффект акцентной полосы слева (черезbox-shadow: inset 5px 0 0 0 var(--accent)), что создаёт чёткий вертикальный маркер приоритета. Это позволяет пользователю мгновенно вычленять важный контент в общем потоке постов, не перегружая карточку лишними графическими элементами.
[!WARNING] Важно
Символ!должен стоять самым первым в названии файла (например,![Soft] Настройка.md), иначе скрипт не распознает его как команду для закрепления.
3.4 Клиентская фильтрация, поиск и Lazy Rendering
Вместо того чтобы нагружать сервер запросами при каждом вводе буквы, поиск реализован на стороне клиента (Client-side Filtering).
- Поиск: Работает мгновенно через
includes()по атрибутамdata-name. - Категории: Переключаются без перезагрузки страницы. Пользователь видит результат сразу.
- Счётчик: Элемент
#articles-countдинамически обновляется, показывая количество найденных записей. Это отличный UX-паттерн. - Интеллектуальная подгрузка (Lazy Rendering): Для обеспечения стабильных 60 FPS при поиске внедрена система постепенного вывода контента. По умолчанию на экран выводится первая сотня совпадений, а доступ к остальному архиву осуществляется через кнопку «Показать ещё». Это исключает «фризы» браузера при работе с тысячами записей, сохраняя при этом полный доступ ко всей базе статей без перезагрузки страницы.
3.4.1 AJAX-управление (Админ-панель)
Для авторизованных пользователей предусмотрена возможность управления архивом:
- Safe Delete: Удаление статей происходит через fetch() с подтверждением confirm(). На стороне сервера в эндпоинте delete_article имя статьи проходит через urllib.parse.unquote(), что гарантирует безошибочное нахождение и удаление файлов с кириллицей, пробелами и спецсимволами. Массовая очистка архива отправляется формой на абсолютный маршрут /blog/articles/delete_all.
- CSRF-защита (базовая): Наличие кастомного заголовка X-Requested-With: XMLHttpRequest в AJAX-запросах защищает от межсайтовой подделки (CSRF), так как браузер блокирует отправку нестандартных заголовков со сторонних ресурсов без явного разрешения CORS.
3.4.2 Технические особенности вёрстки
- CSS Grid: Список постов использует
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)). Это автоматически распределяет карточки по ширине без пустых зон. - Zero-runtime Iconography: Иконки постов (
.post-icon) и маркеры отрисованы на чистом CSS через псевдоэлементы::beforeи::after. Это экономит HTTP-запросы и ускоряет первую отрисовку страницы (LCP). - GPU-ускорение и плавность: Для всех ховеров и анимаций появления используются аппаратные CSS-трансформации, исключая просадки FPS на смартфонах.
- Кастомная навигация: Внедрена авторская стилизация скроллбара (
-webkit-scrollbar) в тёмной теме под акцентный цвет--accent, что исключает появление стандартных «белых» полос прокрутки, выбивающихся из тёмной темы, и обеспечивает визуальную целостность интерфейса.
3.5 Обоснование фронтенд-архитектуры
- No Framework (Vanilla JS): Использование чистого JavaScript – осознанный выбор. Для такого функционала React или Vue были бы избыточны (overkill), а текущий код весит всего пару килобайт и работает быстрее любого фреймворка.
- Jinja2 Hydration: Начальный список статей рендерится сервером, а затем «подхватывается» скриптами для фильтрации. Это сохраняет SEO-показатели (поисковики видят все ссылки) при полной интерактивности.
- LocalStorage Bypass: В отличие от предыдущих модулей, здесь данные полностью контролируются сервером, а фронтенд лишь управляет их отображением.
Плюсы:
- Мгновенная реакция на действия пользователя.
- Идеальное отображение на мобильных устройствах.
- Отсутствие "тяжёлого" JS-бандла.
Минусы:
- При очень большом списке статей (более 1000) клиентский поиск может потреблять больше ресурсов CPU, чем серверный. Но для личного блога это недостижимый предел.
4. Движок чтения статей (Article View Engine)
Этот модуль (article.html) отвечает за рендеринг и интеллектуальное обогащение контента. Основная фишка заключается в Post-processing на стороне клиента. Мы не просто выводим HTML, мы его дорабатываем авторскими скриптами для достижения безупречного UX и полной автономности.
4.1 Клиентская обработка контента (Smart Post-processing)
4.1.1 Оживление текстовых ссылок
Стандартный парсер Markdown часто игнорирует простые текстовые URL, не превращая их в кликабельные элементы. Наш скрипт автоматизирует этот процесс: он находит все строки в тексте соответствующие паттерну https://, и оборачивает их в активные HTML-ссылки. Это гарантирует корректную навигацию, даже если автор статьи забыл использовать синтаксис разметки.
function linkify(node) {
const urlRegex = /(?<!["=])(https?:\/\/[^\s<]+)/g;
let child = node.firstChild;
while (child) {
if (child.nodeType === 3) { // Работаем только с текстовыми узлами
const text = child.nodeValue;
if (urlRegex.test(text)) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = text.replace(urlRegex, (url) =>
`<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`
);
while (tempDiv.firstChild) node.insertBefore(tempDiv.firstChild, child);
const next = child.nextSibling;
node.removeChild(child);
child = next;
continue;
}
} else if (child.nodeType === 1 && !['A', 'PRE', 'BUTTON', 'SCRIPT', 'STYLE'].includes(child.tagName)) {
linkify(child);
}
child = child.nextSibling;
}
}
Инженерная деталь: Используется регулярное выражение с Lookbehind (?<!["=]). Это критически важный предохранитель: он не даёт скрипту испортить уже существующие HTML-теги, где URL находится внутри атрибутов src или href.
4.1.2 Динамический парсинг заголовка (Title Engine)
Здесь применяется логика автоматических визуальных меток. Если заголовок статьи в Markdown-файле имеет вид ![OS] Настройка Windows, скрипт выполняет три задачи:
- Обработка закрепов: Находит символ
!в начале строки и помечает статью как приоритетную, добавляя иконку гвоздика 📌. - Выделение категорий: Извлекает тег
[OS]и преобразует его в эффектный отдельный графический элемент (category-label) над основным названием. - Очистка заголовка: Полностью удаляет технические префиксы (
!и[]) из текста<h1>.
В актуальной версии движка обработка H1 была вынесена из общего цикла JavaScript-обработки текстовых блоков. Теперь за его отображение отвечают строгие CSS-правила с приоритетом !important. Это гарантирует режим Pixel Perfect: заголовок всегда остаётся видимым, не «схлопывается» и корректно переносится по словам даже при наличии сложных символов (например, длинного тире), что исключает любые конфликты со скриптами пост-процессинга.
[!WARNING] Важно
Порядок символов в названии файла критичен: первым должен идти восклицательный знак, затем категория в скобках, и только потом название. Пример:![Soft] Твикер.md.
4.1.3 Система умных спойлеров (Media Spoiler Engine)
- Реализован обработчик атрибута
data-spoiler="true". Если скрипт находит этот атрибут у тегов<img>или<video>, он динамически оборачивает медиа в контейнер.article-spoiler. - Визуальная защита: На контент накладывается блюр 40px и выводится предупреждающая плашка «NSFW / 18+».
- Интерактивность: Контент открывается только после осознанного клика пользователя. При повторном клике блюр возвращается, что обеспечивает приватность просмотра.
4.2 Код и адаптивные таблицы (Code & Tables Engine)
4.2.1 Интеллектуальное оглавление (Smart TOC)
- Автоматическая структуризация (Dynamic TOC): Модуль
Article View Engineсамостоятельно анализирует структуру документа. Если количество заголовковH2иH3превышает три, скрипт динамически инжектирует блок «Содержание» в начало статьи. - Алгоритм иерархии: Подзаголовки
H3визуально отображаются с отступом и пониженной яркостью относительно родительских разделовH2, создавая чёткую логическую карту материала. - Мобильная оптимизация: Для предотвращения чрезмерного растягивания страницы на смартфонах межстрочный интервал в оглавлении принудительно снижен до
1.3, обеспечивая высокую плотность навигационных ссылок.
4.2.2 Блоки кода и механизм копирования (Code Blocks & Copy Engine)
Любой блок программного кода автоматически оборачивается в контейнер .code-block, куда динамически инжектируется кнопка копирования.
- UX-фишка: При клике иконка копирования меняется на галочку (чек-марк) и подсвечивается акцентным цветом на 2 секунды. Это реализует паттерн «мгновенного фидбека», давая пользователю чётко понять: код уже в буфере обмена.
- Шрифты: Для кода жёстко прописаны модульные моноширинные шрифты (
Consolas,Monaco), что критично для профессионального восприятия технических статей.
4.2.3 Адаптивные таблицы (Responsive Data Tables)
Таблицы в вебе — это один из самых «капризных» элементов. Если их просто оставить как есть, они напрочь ломают мобильную вёрстку. В MyBlog реализовано изящное гибридное решение:
4.2.3.1 Серверный этап (Генерация)
Расширение tables в библиотеке markdown превращает стандартную Markdown-разметку:
| Заголовок 1 | Заголовок 2 |
|-------------|-------------|
| Текст 1 | Текст 2 |
в чистый HTML-код: теги <table>, <thead>, <tbody>, <tr> (строки) и <td> (ячейки). Сами по себе эти теги не умеют подстраиваться под размер экрана.
4.2.3.2 Фронтенд-этап (Адаптация)
JS-скрипт в модуле article.html находит все сгенерированные таблицы и оборачивает каждую в контейнер div.table-wrapper со свойством overflow-x: auto. Внутри статьи появляется компактная область, которую можно плавно скроллить пальцем, не ломая общую ширину страницы.
4.3 Дизайн и типографика статьи (Article View Engine)
Модуль рендеринга контента следует философии Modern Dark Minimalism, обеспечивая безупречный UX при чтении сложных технических материалов:
- Оптимизация когнитивной нагрузки: Ширина текстового блока расширена до
900px(класс.content), что даёт простор заголовкам и широким таблицам. - Динамическая адаптивность: Заголовки используют функцию
clamp(26px, 7vw, 42px), что позволяет им органично масштабироваться между огромными 4K-мониторами и экранами компактных смартфонов. - Иерархическая навигация:
- H2 выделены массивным акцентным бордером (
5px solid var(--btn-blue)), выступая в роли визуальных якорей. - H4 дополнены нижним разделением, подчёркивающим структуру подразделов.
- Бейджи категорий автоматически инжектируются в шапку статьи, визуально отделяя тематику (напр. [Soft]) от основного названия.
- H2 выделены массивным акцентным бордером (
- Типографика и оптическая стабилизация: Использование шрифта Tahoma с межстрочным интервалом
1.75и размером параграфов1.15remобеспечивает эталонную читаемость. Чтобы текст не «резал» глаза на идеально чёрном фоне (особенно в браузерах без сглаживания), выбрано мягкое цветовое решение#b0bccd. Это минимизирует нагрузку на зрение при длительном изучении документации. - Мобильная пересборка (Mobile Breakpoints): При достижении ширины в
650pxинтерфейс переходит в мобильный режим:- Навигация трансформируется в горизонтальную ленту с инерционным скроллом.
- Кнопки управления (Экспорт/Поделиться) растягиваются на всю ширину для удобства нажатия пальцем.
- Блоки сравнения изображений (
.compare-wrapper) перестраиваются из горизонтального ряда в вертикальный стек. - Размер шрифта в параграфах снижается до
1.05remдля сохранения информационной плотности. - Навигационный стаб (BackToTop): Для удобства работы с лонгридами внедрена умная кнопка быстрого возврата к шапке статьи. Она появляется автоматически при прокрутке страницы вниз более чем на
400px. Кнопка реализована с использованиемbackdrop-filter: blur(5px), что позволяет ей оставаться заметной на любом фоне, не перекрывая при этом контент и сохраняя общую эстетику тёмной темы.
Визуальная иерархия дополнена умными списками: маркеры ul/ol теперь имеют тот же мягкий оттенок #b0bccd, что и основной текст, и вынесены за пределы текстового блока (list-style-position: outside). Это сохраняет строгий ритм чтения и предотвращает визуальное "слипание" перечислений с основным контентом.
[!WARNING] Важно
Использование фильтра размытия (blur) на кнопке не только выглядит эффектно, но и решает проблему читаемости текста, который оказывается «под» кнопкой во время скролла.
Технический акцент: Весь визуал реализован на чистом CSS без внешних шрифтовых иконок или тяжёлых UI-китов, что гарантирует мгновенную отрисовку страницы (LCP) даже на слабых устройствах.
4.3.1 Нативный Медиа-интерфейс
Для сохранения единого визуального кода и высокого быстродействия проект отказывается от тяжёлых кастомных плееров в пользу нативных элементов браузера. Чтобы стандартные компоненты ОС (на Windows, Android или iOS) не выбивались из общего стиля, применена адаптация через CSS-фильтры:
/* Инверсия цветов с коррекцией тона под тёмную тему */
audio { filter: invert(90%) hue-rotate(180deg) brightness(1.1); }
Это решение позволяет системным контроллерам выглядеть как органичная часть дизайна Modern Dark Minimalism, сохраняя при этом аппаратную оптимизацию и нативное управление громкостью.
Структурированные плейлисты: Для группировки аудиоматериалов реализована поддержка компактных плейлистов на базе связки <figure> и <figcaption>. Подписи к трекам наследуют общую типографику параграфов, превращая набор плееров в упорядоченную библиотеку. Такой подход гарантирует безупречную работу медиа-контента даже на старых устройствах, где кастомные JS-плееры могли бы вызвать задержки в отрисовке (LCP).
4.4 Экспорт (Portable Engine) и системная интеграция (Share API)
Вместо стандартной печати в проект интегрирован механизм сборки автономного HTML-пакета. При нажатии на кнопку «Экспорт» происходит не просто копирование текущего DOM-дерева, а его интеллектуальная «санация»:
- Бинарная инъекция и прогресс: Скрипт асинхронно конвертирует все изображения, аудио и видео в формат Base64. Процесс сопровождается динамическим прогресс-баром (Сборка: X%), что обеспечивает визуальный отклик при работе с объёмными медиа-массивами.
- Интеллектуальная нормализация медиа: В процессе сборки пакета система автоматически декодирует URL-пути, извлекает оригинальные имена файлов (включая кириллицу) и внедряет их в атрибуты title и download. Это подготавливает автономный файл к корректному распознаванию системными загрузчиками ОС.
- Атомарная очистка DOM: Перед генерацией финального Blob-объекта скрипт проводит «санацию» дерева: удаляет интерактивное оглавление (TOC), скрывает служебные кнопки и возвращает блокам кода их первозданный вид (pre), избавляя оффлайн-копию от лишнего «мусора».
- Восстановление кода: Тегам pre возвращается их первозданный вид, без внедрённых скриптами статус-баров. Это гарантирует, что экспортированная статья будет содержать безупречный, валидный код, максимально приближённый к исходному Markdown-оригиналу.
- Сохранение приватности при экспорте: Система спойлеров полностью переносима. При экспорте статьи в автономный HTML-файл все атрибуты data-spoiler и соответствующие CSS-правила сохраняются. Это гарантирует, что даже в оффлайн-копии статьи «горячий» контент останется скрытым до осознанного клика пользователя, сохраняя приватность просмотра вне зависимости от наличия связи с сервером.
Результат: Пользователь получает один .html файл, который открывается в любом браузере без интернета и внешних зависимостей. Это превращает блог в инструмент создания переносимой документации, которую можно хранить вечно.
Цифровая сохранность типографики: Важной особенностью экспорта является полная консервация визуального стиля. Автономная копия статьи сохраняет абсолютную верность типографике: все результаты работы серверных фильтров, включая принудительную ёфикацию и невидимые пробелы (ZWSP), бережно переносятся в HTML-пакет. Это гарантирует, что даже через десятилетия оффлайн-копия будет выглядеть именно так, как её задумал автор, без «разрывов» вёрстки на новых устройствах.
4.4.1 Интеграция с нативной системой (Share API)
Этот модуль отвечает за бесшовную передачу данных между веб-приложением и операционной системой пользователя.
4.4.2 Смарт-нормализация URL
Кнопка Поделиться не просто копирует URL, она выполняет глубокую очистку и интеллектуальное экранирование адреса:
// Декодируем текущий URL, чтобы работать с чистой кириллицей
let currentUrl = decodeURI(window.location.href);
// Принудительно кодируем спецсимволы, критичные для парсеров:
const beautyUrl = currentUrl
.replace(/!/g, '%21')
.replace(/\[P\]/g, '%5B%50%5D')
.replace(/\[/g, '%5B')
.replace(/\]/g, '%5D')
.replace(/ /g, '%20');
Инженерная деталь: Использование decodeURI позволяет получить «чистую» кириллицу, а ручное переопределение кодирования для маркера [P], символов !, [, ] и пробелов гарантирует, что системные теги блога не будут «съедены» парсерами мессенджеров. Это делает ссылку полностью безопасной для передачи в Telegram, Discord или WhatsApp, сохраняя её валидность и аккуратный внешний вид.
4.4.3 Безопасность и управление
- Safe Delete: Кнопка удаления доступна только владельцу (
is_auth) и защищена двойным барьером: проверкой на сервере иconfirmна фронте. - Noopener/Noreferrer: Все внешние ссылки, созданные скриптом, получают эти атрибуты. Это защищает от атак типа
tabnabbingи не передает данные о твоем блоге сторонним ресурсам.
4.4.4 Вердикт по модулю Article
Этот шаблон превращает простой текстовый файл в полноценную веб-страницу.
Главный плюс: Весь функционал (копирование кода, бейджи, ссылки) работает автономно и мгновенно.
Минус: Если в статье будет 500 блоков кода, инициализация 500 кнопок копирования может занять доли микросекунды (что на самом деле незаметно, но мы же задроты, верно?).
5. Галерея и медиа-движок (Gallery & Masonry Engine)
Галерея предназначена для отображения нейросетевых артов и видео. Основная техническая сложность здесь – работа с контентом разной высоты без создания визуальных дыр в сетке.
5.1 Masonry Grid: Математика вёрстки
Вместо использования тяжёлых библиотек (типа Isotope или Masonry.js), в проект интегрирован самописный легковесный алгоритм на Vanilla JS.
- Динамическое распределение: Система определяет количество колонок исходя из ширины экрана (от 1 до 3). Используется массив
colHeights, инициализированный нулями. - Алгоритм кратчайшей колонки: Для каждого элемента вычисляется его позиция: он всегда встаёт в ту колонку, которая в данный момент является самой кратчайшей. Это исключает появление пустых зон (дыр) при разной высоте контента.
- GPU-acceleration: Для перемещения карточек используется
translate3dвместо измененияtop/left. Это задействует аппаратное ускорение видеокарты, обеспечивая плавность 60 FPS при перестроении сетки. - Адаптивная деградация: На экранах менее 700px алгоритм Masonry автоматически отключается (
position: relative), переходя в режим классического вертикального потока. Это экономит ресурсы CPU на слабых смартфонах. - Математическая реализация:
const minH = Math.min(...colHeights), idx = colHeights.indexOf(minH);
item.style.transform = `translate3d(${idx * (itemWidth + gap)}px, ${minH}px, 0)`;
Страховка рендеринга:
Поскольку высота карточки неизвестна до загрузки медиа, вызов resizeInstance дублируется трижды: по событию load, через setTimeout(500) и отдельно по событию onloadedmetadata для каждого видео. Это гарантирует, что сетка не «поплывёт» после того, как тяжёлое видео подгрузит свои размеры.
Динамический расчёт контейнера:
В конце расчёта скрипт находит максимальное значение в colHeights и задаёт высоту родителю: gallery.style.height = Math.max(...colHeights) + "px". Без этого шага последующие элементы страницы (например, футер) «наехали» бы на галерею из-за абсолютного позиционирования элементов.
5.2 Воспроизведение медиа и полноэкранный лайтбокс
5.2.1 Поддержка видео
Галерея нативно поддерживает формат MP4. Для экономии ресурсов и создания "живого" эффекта превью:
- Видео воспроизводятся автоматически (
autoplay), без звука (muted) и по кругу (loop). - Атрибут
playsinlineгарантирует, что видео не будет разворачиваться на весь экран при старте на iPhone. - Атрибут
webkit-playsinlineдля поддержки совсем старых версий iOS, если требуется максимальная совместимость.
Технический нюанс мобильного воспроизведения: В скрипт рендеринга видео автоматически внедрён атрибутplaysinline. Это критически важная деталь для пользователей iOS: без этого параметра браузер Safari на iPhone принудительно разворачивает любое видео в системный полноэкранный плеер при старте. Сplaysinlineвидео остаётся внутри карточки галереи, сохраняя целостность интерфейса и Masonry-сетки.
5.2.2 Умный Лайтбокс (Просмотрщик)
Реализована собственная система полноэкранного предпросмотра. При клике на медиаконтент создаётся оверлей с динамическим размытием заднего плана (backdrop-filter: blur(15px)). Это программно изолирует внимание пользователя на выбранном арте или видео, создавая глубокое погружение и превращая просмотр в полноценный визуальный опыт. Контент внутри лайтбокса адаптивен и ограничен по высоте (95vh), что исключает появление паразитных полос прокрутки и гарантирует корректное отображение на любых типах дисплеев.
Интеллектуальный рендеринг в модальном окне: Скрипт лайтбокса использует прямые ссылки на рабочие медиа-ресурсы и принудительно сбрасывает CSS-фильтры через content.style.filter = "none". Это гарантирует мгновенное отображение чёткого контента, даже если превью в сетке было сильно заблюрено
5.3 Система умных спойлеров (NSFW Spoiler Engine)
В логику рендеринга медиа-карточек интегрирован алгоритм автоматической защиты контента:
- Автоматический блюр в Masonry: Сетка Галереи обучена мгновенно распознавать файлы с суффиксом _S в имени (например, art_S.jpg). Такие карточки по умолчанию рендерятся с экстремальным размытием (blur 40px) и снабжаются сервисным бейджем «18+». Это позволяет безопасно смешивать любой контент в одной ленте, сохраняя эстетику и приличие интерфейса.
- Интерактивная интрига и Лайтбокс: При наведении курсора на заблюренную плитку эффект размытия мягко снижается до 25px, создавая визуальный отклик. Полное «раскрытие» контента происходит исключительно в полноэкранном лайтбоксе: JS-движок подхватывает чистый источник (src) и программно нейтрализует все фильтры цензуры, демонстрируя оригинал в идеальном качестве.
- Бесшовный визуальный код: Использование CSS-фильтров вместо замены исходников позволяет сохранить целостность Masonry-сетки и избежать «прыжков» вёрстки при взаимодействии с контентом. Это гарантирует стабильность позиций элементов даже при экстремальных значениях размытия.
5.4 Интеграция с данными ИИ (Prompt Management)
Каждая карточка в галерее – это контейнер для метаданных.
- Prompt Area: Текстовое поле с промтом по умолчанию скрыто и использует моноширинный шрифт
Consolasдля удобного чтения параметров генерации. - AJAX-сохранение: Для админа реализована возможность редактировать промты прямо "на лету" через
FormDataи эндпоинт/gallery/prompt_save. - Copy Engine: Быстрый механизм копирования промта для тех, кто хочет повторить генерацию.
5.5 Плюсы и минусы решения
Плюсы:
- Zero Library: Отсутствие внешних JS-зависимостей делает страницу экстремально лёгкой.
- GPU acceleration: Плавная перестройка сетки при изменении размера окна браузера.
- Flexibility: Галерея одинаково хорошо работает как с горизонтальными, так и с вертикальными артами.
Минусы:
1. Initial Load: Функция resizeInstance должна ждать полной загрузки изображений, чтобы узнать их высоту (window.onload). На очень медленном интернете сетка может "прыгнуть" во время инициализации. Решается введением фиксированных пропорций для картинок (aspect-ratio), если они известны заранее.
6. Загрузка контента и администрирование (Data Ingestion)
Вся работа с контентом разделена на два специализированных шлюза: upload_media.html и upload_article.html. Они построены на базе нативного JavaScript и FormData, без применения сторонних библиотек.
6.1 Стек и UX-инженерия
Оба загрузчика объединены общим визуальным стилем тёмной темы и реактивным поведением интерфейса:
6.1.1 Интерактивный Drag-and-Drop
Вместо стандартных элементов выбора файлов реализована интерактивная область #dropzone:
- Событийная модель: перехват dragover, dragleave и drop с визуальной сменой рамок и лёгким увеличением масштаба (scale(1.01)).
- Пакетная обработка: поддержка одновременной отправки пачки файлов через единый поток.
6.1.2 Честный трекинг прогресса (XHR over Fetch)
Для отправки данных намеренно выбран XMLHttpRequest, а не fetch. Это архитектурное решение продиктовано необходимостью перехвата события xhr.upload.onprogress, что даёт плавный побайтовый расчёт процентов (progress-bar) в реальном времени даже при передаче гигабайтных видеофайлов.
6.2 Глубокий разбор: Медиа-хаб (upload_media)
Модуль спроектирован как полноценный файловый менеджер для ассетов блога:
6.2.1 Динамическая адресация и теги
- Загрузка осуществляется в любую подпапку внутри
/static/(по умолчаниюgallery). - Система автоматически считывает иерархию через теги и динамически внедряет новые прямо в контейнер
folderTagsContainerбез перезагрузки страницы
6.2.2 Встроенный NSFW-процессинг
Интегрированный чекбокс «Применить блюр (_S)» передаёт флаг is_blur=true. Сервер автоматически дописывает суффикс _S к имени файла, благодаря чему медиа сразу попадает в Галерею с наложенным защитным блюром (40px) и плашкой 18+, исключая случайный показ деликатного контента.
6.2.3 Интегрированный проводник (Live Explorer)
При клике на имя папки разворачивается асинхронный менеджер fileManager (эндпоинт /list_files/{folder_name}): карточки позволяют в один клик скопировать готовый относительный путь /blog/static/... для прямой вставки в Markdown или быстро удалить ненужный файл.
6.3 Глубокий разбор: Загрузка статей (upload_article)
Модуль оптимизирован под быстрый текстовый пайплайн (Obsidian / Markdown):
6.3.1 Валидация и интеллектуальная мутация имён
Фронтенд пропускает исключительно файлы с расширением .md. Перед отправкой через FormData скрипт перехватывает оригинальные имена файлов и модифицирует их на основе выбранных чекбоксов:
- Закреп: чекбокс pinCheck добавляет маркер ! в самое начало имени.
- Приватность: чекбокс privCheck внедряет префикс [P].
- Санитария: скрипт автоматически срезает старые дублирующие префиксы, исключая появление артефактов вида !! [P] [P] Название.
6.3.2 Автоматизация редиректа
После успешного ответа сервера (HTTP 200) статус-бар подтверждает публикацию, и через секунду скрипт выполняет мягкий редирект пользователя на главную страницу /blog/main.
6.3.3 Обоснование инфраструктурных решений
- Zero External Libraries: модуль drag-and-drop и файловый менеджер написаны на чистом Vanilla JS, что гарантирует мгновенную загрузку страниц (~3–5 Кб).
- Борьба с залипанием дескрипторов (Stale Handle Bypass): чтение байтов через
await file.arrayBuffer()и упаковка в свежий Blob при Drag-and-Drop принудительно вычитывают данные с диска в память. Это полностью исключает баг Chromium-браузеров с отправкой устаревшего кэшированного дескриптора при повторном перетаскивании только что изменённого файла. - Сброс состояния инпута: событие onclick на элементе выбора файлов обнуляет
value, гарантируя надёжное срабатываниеonchangeдаже при повторном выборе файла с тем же именем через системный диалог. - Эшелонированная безопасность (Defense in Depth):
- Клиентская валидация типов (acceptи JS-фильтры).
- Серверная чистка имён через регулярные выражения в Python, блокирующая атаки Path Traversal и инъекции спецсимволов. - Mobile-First адаптивность: высота дропзоны динамически уменьшается на экранах до 600px, а элементы управления переводятся в удобный сенсорный формат.
6.4 Административные операции (Destructive Actions)
Проект включает в себя функции моментальной очистки архива:
- Удаление всего (delete_all): защищено двойным барьером — клиентским confirm() и серверной проверкой сессии декоратором require_auth.
- Асинхронное удаление: при удалении отдельной записи используется fetch, исключая перезагрузку всей страницы.
7. Шлюз авторизации (Authentication Gate)
Модуль авторизации (login.html) построен по принципу гибридного шлюза: он обслуживает как полноценного администратора системы, так и гостевой доступ к закрытым материалам.
7.1 UX-инженерия и адаптивность
- Защита от скейла в Safari: для всех полей ввода зафиксирован
font-size: 16px, что предотвращает нежелательный авто-зум в iOS при фокусе. - Интерактивные состояния: форма изолирована в карточке
.login-containerс фоновой подсветкой и акцентным свечением полей ввода при фокусе.
7.2 Логика перенаправлений (The Next-Pattern)
Значение целевого маршрута сохраняется в скрытом поле формы:
<input type="hidden" name="next_url" value="{{ next_url }}">
Если неавторизованный пользователь обращается к закрытому разделу, система перенаправляет его на экран входа с параметром ?next=..., а после подтверждения прав бесшовно возвращает в исходную точку.
7.3 Адаптивный шлюз доступа (Smart Access)
Шаблонизатор Jinja2 динамически меняет архитектуру формы в зависимости от параметра next_url:
- Админ-режим: отображаются стандартные поля «Логин» и «Пароль».
- Приватная статья ([P]): поле логина автоматически скрывается, а форма трансформируется в лаконичный ввод 6-значного кода доступа. Гостю не требуется знать системные учётные записи — доступ открывается строго по ключу.
7.4 Безопасность и менеджмент сессий
- Сессии работают поверх зашифрованных HTTP-only Cookies через
SessionMiddleware. - Все деструктивные операции и доступ к панели управления закрыты серверным барьером
require_auth.
8. Итоги и эксплуатация
8.1 Обоснование технологического стека
- FastAPI: асинхронный, легковесный фреймворк с нативной валидацией и высокой скоростью отклика.
- Uvicorn: скоростной ASGI-сервер. В связке с директивой
LimitNOFILE=65535в systemd процесс получает расширенный пул дескрипторов, обеспечивая параллельную отдачу тяжёлых потоков без сбоев. - Markdown + Vanilla JS: плоские файлы гарантируют надёжность и переносимость базы знаний, а чистый скриптовый слой исключает оверхед тяжёлых фронтенд-библиотек.
- Jinja2: надёжный серверный рендеринг, сохраняющий идеальное SEO без ущерба для динамического поведения.
8.2 Архитектурный вывод (The Big Picture)
- Производительность: связка In-memory кэширования на бэкенде и лимитированного рендеринга на клиенте удерживает отклик интерфейса константным при любом объёме архива.
- Автономия: полное отсутствие внешних СУБД делает проект эталоном self-hosted решений — резервная копия всего блога сводится к простому копированию папки.